package com.whale.util.image;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.http.HttpEntity;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Handler;
import android.widget.ImageView;

import com.whale.util.NetworkUtil;

/**
 * This helper class download images from the Internet and binds those with the
 * provided ImageView.
 * 
 * <p>
 * It requires the INTERNET permission, which should be added to your
 * application's manifest file.
 * </p>
 * 
 * A local cache of downloaded images is maintained internally to improve
 * performance.
 */
public class ImageDownloaderWithCache {
	private static final String LOG_TAG = "ImageDownloaderWithCache";

	public enum Mode {
		NO_ASYNC_TASK, NO_DOWNLOADED_DRAWABLE, CORRECT
	}

	private Mode mode = Mode.CORRECT;

	private Context mContext = null;

	/**
	 * construct
	 */
	public ImageDownloaderWithCache(Context context) {
		mContext = context;
	}

	/**
	 * Download the specified image from the Internet and binds it to the
	 * provided ImageView. The binding is immediate if the image is found in the
	 * cache and will be done asynchronously otherwise. A null bitmap will be
	 * associated to the ImageView if an error occurs.
	 * 
	 * @param url
	 *            The URL of the image to download.
	 * @param imageView
	 *            The ImageView to bind the downloaded image to.
	 */
	public void download(String url, ImageView imageView, int defaultImgRes,
			int inSampleSize, BitmapContainer toSetBmp) {
		resetPurgeTimer();
		Bitmap bitmap = getBitmapFromCache(url);

		if (bitmap == null) {
			forceDownload(url, imageView, defaultImgRes, inSampleSize, toSetBmp);
		} else {
			cancelPotentialDownload(url, imageView);
			imageView.setImageBitmap(bitmap);
			if (toSetBmp != null)
				toSetBmp.bitmap = bitmap;
		}
	}

	public void download(String url, ImageView imageView, int defaultImgRes,
			int inSampleSize) {
		download(url, imageView, defaultImgRes, inSampleSize, null);
	}

	public void download(String url, ImageView imageView, int defaultImgRes) {
		download(url, imageView, defaultImgRes, 0, null);
	}

	/*
	 * Same as download but the image is always downloaded and the cache is not
	 * used. Kept private at the moment as its interest is not clear. private
	 * void forceDownload(String url, ImageView view) { forceDownload(url, view,
	 * null); }
	 */

	/**
	 * Same as download but the image is always downloaded and the cache is not
	 * used. Kept private at the moment as its interest is not clear.
	 * 
	 * @param inSampleSize
	 * @param toSetBmp
	 */
	private void forceDownload(String url, ImageView imageView,
			int defaultImgRes, int inSampleSize, BitmapContainer toSetBmp) {
		// State sanity: url is guaranteed to never be null in
		// DownloadedDrawable and cache keys.
		if (url == null) {
			imageView.setImageDrawable(null);
			return;
		}

		if (cancelPotentialDownload(url, imageView)) {
			switch (mode) {
			case NO_ASYNC_TASK:
				Bitmap bitmap = downloadBitmap(url, 0, 0);
				addBitmapToCache(url, bitmap);
				imageView.setImageBitmap(bitmap);
				break;

			case NO_DOWNLOADED_DRAWABLE:
				imageView.setMinimumHeight(156);
				BitmapDownloaderTask task = new BitmapDownloaderTask(imageView,
						inSampleSize, toSetBmp);
				task.execute(url);
				break;

			case CORRECT:
				task = new BitmapDownloaderTask(imageView, inSampleSize,
						toSetBmp);
				// 得到Resources对象
				Resources res = mContext.getResources();
				// 以数据流的方式读取资源
				InputStream is = res.openRawResource(defaultImgRes);
				DownloadedDrawable downloadedDrawable = new DownloadedDrawable(
						task, res, is);
				imageView.setImageDrawable(downloadedDrawable);
				imageView.setMinimumHeight(156);
				task.execute(url);
				break;
			}
		}
	}

	/**
	 * Returns true if the current download has been canceled or if there was no
	 * download in progress on this image view. Returns false if the download in
	 * progress deals with the same url. The download is not stopped in that
	 * case.
	 */
	private static boolean cancelPotentialDownload(String url,
			ImageView imageView) {
		BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);

		if (bitmapDownloaderTask != null) {
			String bitmapUrl = bitmapDownloaderTask.url;
			if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
				bitmapDownloaderTask.cancel(true);
			} else {
				// The same URL is already being downloaded.
				return false;
			}
		}
		return true;
	}

	/**
	 * @param imageView
	 *            Any imageView
	 * @return Retrieve the currently active download task (if any) associated
	 *         with this imageView. null if there is no such task.
	 */
	private static BitmapDownloaderTask getBitmapDownloaderTask(
			ImageView imageView) {
		if (imageView != null) {
			Drawable drawable = imageView.getDrawable();
			if (drawable instanceof DownloadedDrawable) {
				DownloadedDrawable downloadedDrawable = (DownloadedDrawable) drawable;
				return downloadedDrawable.getBitmapDownloaderTask();
			}
		}
		return null;
	}

	public Bitmap downloadBitmap(String url, int inSampleSize, int tryISMode) {
		HttpEntity entity = null;
		InputStream inputStream = null;
		try {
			BitmapFactory.Options options = new BitmapFactory.Options();
			if (inSampleSize > 0) {
				options.inSampleSize = 4;
			}
			entity = NetworkUtil.httpGetURLHE(url);
			if (entity != null) {
				inputStream = entity.getContent();
				// return BitmapFactory.decodeStream(inputStream);
				// Bug on slow connections, fixed in future release.
				// 请查看 Android Bug:
				// http://code.google.com/p/android/issues/detail?id=6066
				// http://stackoverflow.com/questions/3802820/bitmapfactory-decodestream-always-returns-null-and-skia-decoder-shows-decode-retu
				if (tryISMode == 0) {
					Bitmap bitmap = BitmapFactory.decodeStream(
							new FlushedInputStream(inputStream), null, options);
					if (bitmap != null) {
						return bitmap;
					} else {
						return downloadBitmap(url, inSampleSize, 1);
					}
				} else {
					return BitmapFactory.decodeStream(new PlurkInputStream(
							inputStream), null, options);
				}
			} else {
				return null;
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (inputStream != null) {
					inputStream.close();
				}
				if (entity != null) {
					entity.consumeContent();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return null;
	}

	/*
	 * An InputStream that skips the exact number of bytes provided, unless it
	 * reaches EOF.
	 */
	static class FlushedInputStream extends FilterInputStream {
		public FlushedInputStream(InputStream inputStream) {
			super(inputStream);
		}

		@Override
		public long skip(long n) throws IOException {
			long totalBytesSkipped = 0L;
			while (totalBytesSkipped < n) {
				long bytesSkipped = in.skip(n - totalBytesSkipped);
				if (bytesSkipped == 0L) {
					int b = read();
					if (b < 0) {
						break; // we reached EOF
					} else {
						bytesSkipped = 1; // we read one byte
					}
				}
				totalBytesSkipped += bytesSkipped;
			}
			return totalBytesSkipped;
		}
	}

	/**
	 * The actual AsyncTask that will asynchronously download the image.
	 */
	class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
		private String url;
		private final WeakReference<ImageView> imageViewReference;

		private int inSampleSize;
		private BitmapContainer toSetBmp;

		public BitmapDownloaderTask(ImageView imageView, int inSampleSize,
				BitmapContainer toSetBmp) {
			imageViewReference = new WeakReference<ImageView>(imageView);
			this.inSampleSize = inSampleSize;
			this.toSetBmp = toSetBmp;
		}

		/**
		 * Actual download method.
		 */
		@Override
		protected Bitmap doInBackground(String... params) {
			url = params[0];
			return downloadBitmap(url, this.inSampleSize, 0);
		}

		/**
		 * Once the image is downloaded, associates it to the imageView
		 */
		@Override
		protected void onPostExecute(Bitmap bitmap) {
			if (isCancelled()) {
				bitmap = null;
			}
			if (bitmap != null) {
				addBitmapToCache(url, bitmap);

				if (imageViewReference != null) {
					ImageView imageView = imageViewReference.get();
					BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
					// Change bitmap only if this process is still associated
					// with it
					// Or if we don't use any bitmap to task association
					// (NO_DOWNLOADED_DRAWABLE mode)
					if ((this == bitmapDownloaderTask)
							|| (mode != Mode.CORRECT)) {
						imageView.setImageBitmap(bitmap);
						if (toSetBmp != null)
							toSetBmp.bitmap = bitmap;
					}
				}
			}
		}
	}

	/**
	 * A fake Drawable that will be attached to the imageView while the download
	 * is in progress.
	 * 
	 * <p>
	 * Contains a reference to the actual download task, so that a download task
	 * can be stopped if a new binding is required, and makes sure that only the
	 * last started download process can bind its result, independently of the
	 * download finish order.
	 * </p>
	 */
	static class DownloadedDrawable extends BitmapDrawable {
		private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;

		public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask,
				Resources res, InputStream is) {
			super(res, is);
			bitmapDownloaderTaskReference = new WeakReference<BitmapDownloaderTask>(
					bitmapDownloaderTask);
		}

		public BitmapDownloaderTask getBitmapDownloaderTask() {
			return bitmapDownloaderTaskReference.get();
		}
	}

	public void setMode(Mode mode) {
		this.mode = mode;
		clearCache();
	}

	/*
	 * Cache-related fields and methods.
	 * 
	 * We use a hard and a soft cache. A soft reference cache is too
	 * aggressively cleared by the Garbage Collector.
	 */

	private static final int HARD_CACHE_CAPACITY = 10;
	private static final int DELAY_BEFORE_PURGE = 10 * 1000; // in milliseconds

	// Hard cache, with a fixed maximum capacity and a life duration
	private final HashMap<String, Bitmap> sHardBitmapCache = new LinkedHashMap<String, Bitmap>(
			HARD_CACHE_CAPACITY / 2, 0.75f, true) {
		@Override
		protected boolean removeEldestEntry(
				LinkedHashMap.Entry<String, Bitmap> eldest) {
			if (size() > HARD_CACHE_CAPACITY) {
				// Entries push-out of hard reference cache are transferred to
				// soft reference cache
				sSoftBitmapCache.put(eldest.getKey(),
						new SoftReference<Bitmap>(eldest.getValue()));
				return true;
			} else
				return false;
		}
	};

	// Soft cache for bitmaps kicked out of hard cache
	private final static ConcurrentHashMap<String, SoftReference<Bitmap>> sSoftBitmapCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>(
			HARD_CACHE_CAPACITY / 2);

	private final Handler purgeHandler = new Handler();

	private final Runnable purger = new Runnable() {
		public void run() {
			clearCache();
		}
	};

	/**
	 * Adds this bitmap to the cache.
	 * 
	 * @param bitmap
	 *            The newly downloaded bitmap.
	 */
	private void addBitmapToCache(String url, Bitmap bitmap) {
		if (bitmap != null) {
			synchronized (sHardBitmapCache) {
				sHardBitmapCache.put(url, bitmap);
			}
		}
	}

	/**
	 * @param url
	 *            The URL of the image that will be retrieved from the cache.
	 * @return The cached bitmap or null if it was not found.
	 */
	private Bitmap getBitmapFromCache(String url) {
		// First try the hard reference cache
		synchronized (sHardBitmapCache) {
			final Bitmap bitmap = sHardBitmapCache.get(url);
			if (bitmap != null) {
				// Bitmap found in hard cache
				// Move element to first position, so that it is removed last
				sHardBitmapCache.remove(url);
				sHardBitmapCache.put(url, bitmap);
				return bitmap;
			}
		}

		// Then try the soft reference cache
		SoftReference<Bitmap> bitmapReference = sSoftBitmapCache.get(url);
		if (bitmapReference != null) {
			final Bitmap bitmap = bitmapReference.get();
			if (bitmap != null) {
				// Bitmap found in soft cache
				return bitmap;
			} else {
				// Soft reference has been Garbage Collected
				sSoftBitmapCache.remove(url);
			}
		}

		return null;
	}

	/**
	 * Clears the image cache used internally to improve performance. Note that
	 * for memory efficiency reasons, the cache will automatically be cleared
	 * after a certain inactivity delay.
	 */
	public void clearCache() {
		sHardBitmapCache.clear();
		sSoftBitmapCache.clear();
	}

	/**
	 * Allow a new delay before the automatic cache clear is done.
	 */
	private void resetPurgeTimer() {
		purgeHandler.removeCallbacks(purger);
		purgeHandler.postDelayed(purger, DELAY_BEFORE_PURGE);
	}

	/**
	 * @param image_url
	 * @param ivPic
	 * @param small_bitmap
	 *            to Set for control cache
	 */
	public void download(String image_url, ImageView ivPic, int defaultImgRes,
			BitmapContainer small_bitmap) {
		download(image_url, ivPic, defaultImgRes, 0, small_bitmap);
	}

	public static class PlurkInputStream extends FilterInputStream {

		protected PlurkInputStream(InputStream in) {
			super(in);
		}

		@Override
		public int read(byte[] buffer, int offset, int count)
				throws IOException {
			int ret = super.read(buffer, offset, count);
			for (int i = 6; i < buffer.length - 4; i++) {
				if (buffer[i] == 0x2c) {
					if (buffer[i + 2] == 0 && buffer[i + 1] > 0
							&& buffer[i + 1] <= 48) {
						buffer[i + 1] = 0;
					}
					if (buffer[i + 4] == 0 && buffer[i + 3] > 0
							&& buffer[i + 3] <= 48) {
						buffer[i + 3] = 0;
					}
				}
			}
			return ret;
		}

	}

}